﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

using Boggle;

using NUnit.Framework;

namespace Boggle.Tests
{
    public class TestBoggleGrid
	{
		#region Ctor

		[Test, TestCaseSource("GetCtorCases")]
        public void TestCtor(Die[,] dice)
        {
            var g = new BoggleGrid(dice);
        }

        private IEnumerable<ITestCaseData> GetCtorCases()
        {
            yield return new TestCaseData(null)
                .Throws<ArgumentNullException>();

            yield return new TestCaseData(new Die[,] { })
                .Throws<ArgumentException>();

            yield return new TestCaseData(
                new Die[4, 4] { {new Die("a"), new Die("a"), new Die("a"), new Die("a")}, 
                                {new Die("a"), new Die("a"), new Die("a"), new Die("a")},
                                {new Die("a"), new Die("a"), null, new Die("a")},
                                {new Die("a"), new Die("a"), new Die("a"), new Die("a")} })
                .Throws<ArgumentException>();
        }

        [Test, TestCaseSource("GetContainsWordCases")]
        public List<String> TestContainsWord(BoggleGrid grid, String word)
        {
            var dice = grid.ContainsWord(word);
            return dice == null ? null : dice.Select(d => d.CurrentValue).ToList();
        }

		#endregion

		#region ContainsWord

		private IEnumerable<ITestCaseData> GetContainsWordCases()
        {
            var referenceGrid = new BoggleGrid(
                new Die[4, 4]{ { new Die("a"), new Die("b"), new Die("c"), new Die("d") },
                               { new Die("b"), new Die("b"), new Die("c"), new Die("d") },
                               { new Die("c"), new Die("c"), new Die("c"), new Die("d") },
                               { new Die("d"), new Die("d"), new Die("d"), new Die("d") } });

            yield return new TestCaseData(
                referenceGrid,
                null)
                .Returns(null);

            yield return new TestCaseData(
                referenceGrid,
                String.Empty)
                .Returns(new List<String> { });

            yield return new TestCaseData(
                referenceGrid,
                "a")
                .Returns(new List<String> { "a" });

            yield return new TestCaseData(
                referenceGrid,
                "e")
                .Returns(null);

            yield return new TestCaseData(
                referenceGrid,
                "abc")
                .Returns(new List<String> { "a", "b", "c" });

            yield return new TestCaseData(
                referenceGrid,
                "acb")
                .Returns(null);

            referenceGrid = new BoggleGrid(
                new Die[4, 4]{ { new Die("a"), new Die("b"), new Die("c"), new Die("d") },
                               { new Die("e"), new Die("f"), new Die("g"), new Die("h") },
                               { new Die("i"), new Die("j"), new Die("k"), new Die("l") },
                               { new Die("m"), new Die("n"), new Die("o"), new Die("p") } });

            yield return new TestCaseData(
                referenceGrid,
                "ab")
                .Returns(new List<String> { "a", "b" });

            yield return new TestCaseData(
                referenceGrid,
                "af")
                .Returns(new List<String> { "a", "f" });

            yield return new TestCaseData(
                referenceGrid,
                "ae")
                .Returns(new List<String> { "a", "e" });

            yield return new TestCaseData(
                referenceGrid,
                "aba")
                .Returns(null);

            yield return new TestCaseData(
                referenceGrid,
                "afkplhdcgjno")
                .Returns(new List<String> { "a", "f", "k", "p", "l", "h", "d", "c", "g", "j", "n", "o" });

            yield return new TestCaseData(
                referenceGrid,
                "jklhba")
                .Returns(null);

            yield return new TestCaseData(
                referenceGrid,
                "aeim")
                .Returns(new List<String> { "a", "e", "i", "m" });

            referenceGrid = new BoggleGrid(
                new Die[5, 4]{ { new Die("a"), new Die("b"), new Die("c"), new Die("d") },
                               { new Die("e"), new Die("f"), new Die("g"), new Die("h") },
                               { new Die("i"), new Die("j"), new Die("k"), new Die("l") },
                               { new Die("m"), new Die("n"), new Die("o"), new Die("p") },
                               { new Die("q"), new Die("r"), new Die("s"), new Die("t") }});

            yield return new TestCaseData(
                referenceGrid,
                "tpos")
                .Returns(new List<String> { "t", "p", "o", "s" });

            yield return new TestCaseData(
                referenceGrid,
                "aebf")
                .Returns(new List<String> { "a", "e", "b", "f" });

            yield return new TestCaseData(
                referenceGrid,
                "qmnr")
                .Returns(new List<String> { "q", "m", "n", "r" });
		}

		#endregion

		#region Dice

		/// <summary>
		/// Tests the Dice method.
		/// </summary>
		[Test, TestCaseSource("GetDiceCases")]
		public List<Die> TestDice(Die[,] dice)
		{
			var grid = new BoggleGrid(dice);
			return grid.Dice;
		}

		/// <summary>
		/// Provides test cases for the Dice test.
		/// </summary>
		/// <returns>Test cases for the Dice test method.</returns>
		public IEnumerable<ITestCaseData> GetDiceCases()
		{
			var a = new Die("a");
			var b = new Die("b");
			var c = new Die("c");
			var d = new Die("d");
			var e = new Die("e");
			var f = new Die("f");
			var g = new Die("g");
			var h = new Die("h");
			var i = new Die("i");
			var j = new Die("j");
			var k = new Die("k");
			var l = new Die("l");
			var m = new Die("m");
			var n = new Die("n");
			var o = new Die("o");
			var p = new Die("p");

			yield return new TestCaseData(new Die[4, 4]{ { a, b, c, d },
														 { e, f, g, h },
														 { i, j, k, l },
														 { m, n, o, p } })
				.Returns(new List<Die>{a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p});
		}

		#endregion

        #region Shuffle

        /// <summary>
        /// Tests the fairness of the Shuffle algorithm.
        /// </summary>
        [Test]
        public void TestShuffle()
        {
            var combinations = 24;
            var idealFrequency = 1000;
            var dies = Enumerable.Range(65, 16).Select(f => new Die(Char.ConvertFromUtf32(f))).ToArray();
            var i = 0;
            var dice = new Die[2,2]{ {dies[i++], dies[i++]},
                                     {dies[i++], dies[i++]} };
            var grid = new BoggleGrid(dice);
            var frequencies = new Dictionary<String, Int32>();

            for (i = 0; i < combinations * idealFrequency; i++)
            {
                grid.Shuffle();
                var key = grid.Dice.Aggregate(new StringBuilder(), (acc, die) => acc.Append(die.CurrentValue)).ToString();

                if (!frequencies.ContainsKey(key))
                {
                    frequencies.Add(key, 0);
                }

                frequencies[key]++;
            }

            Assert.AreEqual(combinations, frequencies.Count);
            var acceptableUpperBound = idealFrequency * 1.1;
            var acceptableLowerBound = idealFrequency * 0.9;

            foreach (var result in frequencies)
            {
                Trace.WriteLine(String.Format("{0} shuffled {1} times", result.Key, result.Value));
            }

            if (frequencies.Select(f => f.Value).Any(v => v > acceptableUpperBound || v < acceptableLowerBound))
            {
                Assert.Inconclusive("The die rolls do not appear to be fair");
            }
        }

        #endregion

        #region ToString

        /// <summary>
        /// Tests the ToString method.
        /// </summary>
        /// <param name="dice"></param>
        /// <returns></returns>
        [Test, TestCaseSource("GetToStringCases")]
        public String TestToString(Die[,] dice)
        {
            return new BoggleGrid(dice).ToString();
        }

        /// <summary>
        /// Provides test cases for the ToString method.
        /// </summary>
        /// <returns></returns>
        private IEnumerable<ITestCaseData> GetToStringCases()
        {
            var a = new Die("a");
            var b = new Die("b");
            var c = new Die("c");
            var d = new Die("d");
            var e = new Die("e");
            var f = new Die("f");
            var g = new Die("g");
            var h = new Die("h");
            var i = new Die("i");
            var j = new Die("j");
            var k = new Die("k");
            var l = new Die("l");
            var m = new Die("m");
            var n = new Die("n");
            var o = new Die("o");
            var p = new Die("p");
            var q = new Die("q");
            var r = new Die("r");
            var s = new Die("s");
            var t = new Die("t");

            yield return new TestCaseData(new Die[4, 4]{ { a, b, c, d },
														 { e, f, g, h },
														 { i, j, k, l },
														 { m, n, o, p } })
                .Returns("a   b   c   d   \r\ne   f   g   h   \r\ni   j   k   l   \r\nm   n   o   p   \r\n");

            yield return new TestCaseData(new Die[5, 4]{ { a, b, c, d },
														 { e, f, g, h },
														 { i, j, k, l },
														 { m, n, o, p },
                                                         { q, r, s, t } })
                .Returns("a   b   c   d   \r\ne   f   g   h   \r\ni   j   k   l   \r\nm   n   o   p   \r\nq   r   s   t   \r\n");

            yield return new TestCaseData(new Die[4, 5]{ { a, b, c, d, e },
														 { f, g, h, i, j },
														 { k, l, m, n, o },
														 { p, q, r, s, t } })
                .Returns("a   b   c   d   e   \r\nf   g   h   i   j   \r\nk   l   m   n   o   \r\np   q   r   s   t   \r\n");
        }

        #endregion
    }
}
